package com.example.zhaoshuang.rippledemo;

import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.*;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.BlendMode;
import ohos.agp.render.Canvas;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.agp.window.service.Window;
import ohos.agp.window.service.WindowManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.PixelMap;
import ohos.media.image.common.*;
import ohos.multimodalinput.event.KeyEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * Created by zhaoshuang on 16/8/29.
 * 弹出动画的popupwindow
 */
public class HintPopupWindow {

    private static final String TAG = "HintPopupWindow";
    private static final HiLogLabel HILOGLABEL = new HiLogLabel(HiLog.LOG_APP, 0x00000, TAG);
    /**
     * 动画执行
     */
    private static final int ANIMDURATION = 250;

    private final AbilitySlice activity;
    private WindowManager.LayoutConfig params;
    private boolean isShow;
    private final WindowManager windowManager;
    private ComponentContainer rootView;
    private ComponentContainer linearLayout;
    private Window currWindow;

    private boolean isAniming;//动画是否在执行

    /**
     * @param contentList 点击item的内容文字
     * @param clickList   点击item的事件
     *                    文字和click事件的list是对应绑定的
     */
    public HintPopupWindow(AbilitySlice activity, List<String> contentList, List<Component.ClickedListener> clickList) {

        this.activity = activity;
        windowManager = WindowManager.getInstance();

        initLayout(contentList, clickList);
    }

    /**
     * @param contentList 点击item内容的文字
     * @param clickList   点击item的事件
     */
    public void initLayout(List<String> contentList, List<Component.ClickedListener> clickList) {
        LayoutScatter layoutScatter = LayoutScatter.getInstance(activity);
        //这是根布局
        rootView = (ComponentContainer) layoutScatter.parse(ResourceTable.Layout_item_root_hintpopupwindow, null, false);
        linearLayout = (ComponentContainer) rootView.findComponentById(ResourceTable.Id_linearLayout);

        //格式化点击item, 将文字和click事件一一绑定上去
        List<Component> list = new ArrayList<>();
        for (int x = 0; x < contentList.size(); x++) {
            Component view = layoutScatter.parse(ResourceTable.Layout_item_hint_popupwindow, null, false);
            Text textView = (Text) view.findComponentById(ResourceTable.Id_tv_content);
            Component v_line = view.findComponentById(ResourceTable.Id_v_line);
            textView.setText(contentList.get(x));
            linearLayout.addComponent(view);
            list.add(view);
            if (x == 0) {
                v_line.setVisibility(Component.INVISIBLE);
            } else {
                v_line.setVisibility(Component.VISIBLE);
            }
        }
        for (int x = 0; x < list.size(); x++) {
            list.get(x).setClickedListener(clickList.get(x));
        }

        //这里给你根布局设置背景透明, 为的是让他看起来和activity的布局一样
        params = new WindowManager.LayoutConfig();
        params.width = ComponentContainer.LayoutConfig.MATCH_PARENT;
        params.height = ComponentContainer.LayoutConfig.MATCH_PARENT;
        params.alignment = LayoutAlignment.LEFT | LayoutAlignment.TOP;

        //当点击根布局时, 隐藏
        rootView.setClickedListener(v -> dismissPopupWindow());

        rootView.setKeyEventListener((component, keyEvent) -> {
            boolean result = isShow;
            //如果是显示状态那么隐藏视图
            if (KeyEvent.KEY_BACK == keyEvent.getKeyCode() && isShow) {
                dismissPopupWindow();
            }
            return result;
        });
    }

    /**
     * 弹出选项弹窗
     *
     * @param locationView 默认在该view的下方弹出, 和popupWindow类似
     */
    public void showPopupWindow(Component locationView) {
        if (locationView == null) {
            return;
        }
        logInfo("showPopupWindow: " + isAniming);
        if (!isAniming) {
            isAniming = true;
            isShow = true;
            try {
                //获取根布局
                Component rootComponent = getSliceRootComponent(locationView);
                //获取根布局在屏幕的起始位置（可计算status bar的高度）
                int[] rootPos = rootComponent.getLocationOnScreen();
                //这个步骤是得到该view相对于屏幕的坐标, 注意不是相对于父布局哦!
                int[] arr = locationView.getLocationOnScreen();
                linearLayout.estimateSize(0, 0);
                Point screen = new Point();
                Optional<Display> optional = DisplayManager.getInstance().getDefaultDisplay(activity);
                optional.ifPresent(display -> display.getSize(screen));
                int x = Math.max(0, arr[0] + locationView.getWidth() - linearLayout.getEstimatedWidth());
                int y = Math.max(0, arr[1] - rootPos[1] + locationView.getHeight());
                if (y + linearLayout.getEstimatedHeight() > screen.getPointYToInt()) {
                    y = screen.getPointYToInt() - locationView.getHeight() - linearLayout.getEstimatedHeight();
                }
                if (y < 0) {
                    y = 0;
                }
                linearLayout.setTranslationX(x);
                linearLayout.setTranslationY(y);
                //在open harmony中，解决第一次显示闪动问题
                linearLayout.setScale(0f, 0f);
                rootView.setAlpha(1.0f);

                alphaAnim(rootView, 0, 1);
                if (rootView.getBackgroundElement() != null) {
                    rootComponent.setBackground(null);
                }

                //这里就是使用WindowManager直接将我们处理好的view添加到屏幕最前端
                currWindow = windowManager.addComponent(rootView, activity, 0);
                currWindow.setLayoutConfig(params);
                currWindow.setTransparent(true);

                //这一步就是有回弹效果的弹出动画, 我用属性动画写的, 很简单
                showAnim(linearLayout, 0, 1, ANIMDURATION, true);

                //视图被弹出来时得到焦点, 否则就捕获不到Touch事件
                rootView.setFocusable(Component.FOCUS_ENABLE);
                rootView.setTouchFocusable(true);
                rootView.requestFocus();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 得到bitmap位图, 传入View对象
     * todo 待在openharmony上实现将底层布局的内容绘制到其他canvas上周，此处放开访问权限
     */
    private static PixelMap getBitmapByView(Component view) {
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.pixelFormat = PixelFormat.ARGB_8888;
        options.size = new Size(view.getEstimatedWidth(), view.getEstimatedHeight());
        PixelMap bitmap = PixelMap.create(options);
        //todo 此处应该是把控件内容绘制输出到指定的PixelMap上，open harmony上暂未找到处理方式
        Canvas canvas = new Canvas(new Texture(bitmap));
        canvas.drawColor(Color.getIntColor("#30FFFFFF"), BlendMode.SRC);
        return bitmap;
    }

    private void setBlurBackground(PixelMap bitmap) {
        int bitmapWidth = bitmap.getImageInfo().size.width;
        int bitmapHeight = bitmap.getImageInfo().size.height;
        int scaledWidth = bitmapWidth / 3;
        int scaledHeight = bitmapHeight / 3;
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.size = new Size(scaledWidth, scaledHeight);
        options.scaleMode = ScaleMode.CENTER_CROP;
        options.editable = true;
        PixelMap scaledBitmap = PixelMap.create(bitmap, options);

        PixelMap blurBitmap = getBlurBitmap(scaledBitmap, 5);
        rootView.setBackground(new PixelMapElement(blurBitmap));
        alphaAnim(rootView, 0, 1);
    }

    private static PixelMap getBlurBitmap(PixelMap bitmap, int radius) {
        return blurBitmap(bitmap, radius);
    }

    /**
     * 模糊位图（高斯模糊）
     *
     * @param sentBitmap 原始位图
     * @param radius     模糊等级 >=0 && <=25
     * @return 模糊后的PixelMap
     */
    private static PixelMap blurBitmap(PixelMap sentBitmap, final int radius) {
        if (!sentBitmap.isEditable()) {
            return sentBitmap;
        }

        if (radius < 1) {
            return (null);
        }

        int w = sentBitmap.getImageInfo().size.width;
        int h = sentBitmap.getImageInfo().size.height;

        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.editable = true;
        options.size = new Size(w, h);
        options.pixelFormat = PixelFormat.ARGB_8888;
        options.alphaType = AlphaType.UNPREMUL;
        PixelMap output = PixelMap.create(options);

        int[] pix = new int[w * h];
        sentBitmap.readPixels(pix, 0, w, new Rect(0, 0, w, h));

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int[] r = new int[wh];
        int[] g = new int[wh];
        int[] b = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int[] vmin = new int[Math.max(w, h)];

        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int[] dv = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }
        output.writePixels(pix, 0, w, new Rect(0, 0, w, h));
        return output;
    }

    public void dismissPopupWindow() {
        logInfo("dismissPopupWindow: " + isAniming);
        if (!isAniming) {
            isAniming = true;
            isShow = false;
            goneAnim(linearLayout, 0.95f, 1, ANIMDURATION / 3, true);
        }
    }

    public WindowManager.LayoutConfig getLayoutParams() {
        return params;
    }

    public ComponentContainer getLayout() {
        return linearLayout;
    }

    /**
     * popupwindow是否是显示状态
     */
    public boolean isShow() {
        return isShow;
    }

    private void alphaAnim(final Component view, int start, int end) {
        new AnimatorProperty(view).setDuration(ANIMDURATION).alphaFrom(start).alpha(end).start();
    }

    private void showAnim(final Component view, float start, final float end, int duration, final boolean isWhile) {
        AnimatorValue va = createShowOrGoneAnimator(view, start, end, duration);
        va.setStateChangedListener(getAnimStateChangeListener(unused -> {
            if (isWhile) {
                showAnim(view, end, 0.95f, ANIMDURATION / 3, false);
            } else {
                isAniming = false;
            }
        }));
        va.start();
    }

    public void goneAnim(final Component view, float start, final float end, int duration, final boolean isWhile) {
        AnimatorValue va = createShowOrGoneAnimator(view, start, end, duration);
        va.setStateChangedListener(getAnimStateChangeListener(unused -> {
            if (isWhile) {
                alphaAnim(rootView, 1, 0);
                goneAnim(view, end, 0f, ANIMDURATION, false);
            } else {
                try {
                    windowManager.destroyWindow(currWindow);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    ComponentParent parent = rootView.getComponentParent();
                    parent.removeComponent(rootView);
                }
                isAniming = false;
            }
        }));
        va.start();
    }

    /**
     * 根据动画的当前值，计算实际在起始值和结束值之间的值
     *
     * @param start 起始值
     * @param end   结束值
     * @param curr  动画的当前值
     * @return 实际值
     */
    private double calAnimValue(double start, double end, double curr) {
        return start + (end - start) * curr;
    }

    private void logInfo(String text) {
        HiLog.info(HILOGLABEL, text);
    }

    /**
     * 创建显示或隐藏的值变化动画
     *
     * @param view     组件
     * @param start    起始值
     * @param end      结束值
     * @param duration 动画时长
     * @return 值变化动画
     */
    private AnimatorValue createShowOrGoneAnimator(Component view, float start, float end, int duration) {
        AnimatorValue va = new AnimatorValue();
        va.setDuration(duration);
        va.setValueUpdateListener((animatorValue, v) -> {
            view.setPivotX(view.getWidth());
            view.setPivotY(0);
            float result = (float) calAnimValue(start, end, v);
            view.setScaleX(result);
            view.setScaleY(result);
        });
        return va;
    }

    /**
     * 获取{@link Component}所在布局的根布局
     *
     * @param component 目标布局，将获取该布局的最顶级布局
     * @return 根布局
     */
    private Component getSliceRootComponent(Component component) {
        if (component.getComponentParent() == null) {
            return component;
        }
        return getSliceRootComponent((ComponentContainer) component.getComponentParent());
    }

    /**
     * 获取{@link Animator.StateChangedListener}的监听实例
     *
     * @param function 动画end状态时的执行
     * @return {@link Animator.StateChangedListener}的实例
     */
    private Animator.StateChangedListener getAnimStateChangeListener(Consumer<Void> function) {
        return new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                if (function != null) {
                    function.accept(null);
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        };
    }
}
